/*
 * drivers/mmc/host/sdhci-mc.c - MC SDHCI Platform driver
 *
 * Copyright (c) 2021-2022, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/delay.h>
#include <linux/mmc/mmc.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include "sdhci-pltfm.h"
#include <linux/irqdomain.h>
#include <mach/pmu.h>
#include <mach/io.h>
#include <linux/gpio.h>



#define EMMC_PHY_VERSION_ID		(0x0)
#define EMMC_PHY_INT_EN			(0x4)
#define EMMC_PHY_INT_SRC		(0x08)
#define EMMC_PHY_CTRL			(0x0c)
#define EMMC_PHY_TEST_O			(0x10)
#define EMMC_PHY_TEST_I			(0x14)
#define EMMC_PHY_IE_CTRL		(0x18)
#define EMMC_PHY_DL_CTRL		(0x1c)
#define EMMC_PHY_INT_MASK		(0x20)
#define EMMC_PHY_INT_CTRL2		(0x24)
/**
* INT_SRC
*/
#define	EMMC_PHY_MODE		BIT(0)
#define	LOOPBACK_MODE		BIT(1)
#define	PRBS_GEN_EN			BIT(2)
#define	PRBS_CHK_EN			BIT(3)
#define	PRBS_MODE			BIT(4)
#define	PRBS_CHK_IN_SEL_OFS	6
#define	PRBS_CHK_IN_SEL_MSK	(0xf << PRBS_CHK_IN_SEL_OFS)
#define	TX_CLK_INV_EN		BIT(9)
#define	TX_CLK_GATE			BIT(10)
#define	RX_CLK_SEL_DAT		BIT(11)
#define	RX_CLK_SEL_CMD		BIT(12)
#define	RX_CLK_INV_EN_DAT	BIT(13)
#define	RX_CLK_INV_EN_CMD	BIT(14)
#define	TX_DL_BYPASS		BIT(15)
#define	RX_DL_BYPASS_DAT	BIT(16)
#define	RX_DL_BYPASS_CMD	BIT(17)
#define	PRBS_GEN_OUT_EN_OFS	18
#define	PRBS_GEN_OUT_EN_MSK	(0x1ff << PRBS_GEN_OUT_EN_OFS)
#define	FIFO_CLR			BIT(27)
#define	SAMPLE_EN			BIT(28)
#define	OUTPUT_BIT_MODE_OFS	29
#define	OUTPUT_BIT_MODE_MSK	(0x3 << OUTPUT_BIT_MODE_OFS)
#define	DAT_INPUT_EN		BIT(31)
/**
* IE_CTRL
*/
#define 	IE_CTRL_CMD   		BIT(0)
#define 	IE_CTRL_DAT_OFS		1
#define 	IE_CTRL_DAT_MSK		0xff
#define 	CARD_RESET_N		BIT(9)
/**
* DL_CTRL
*/
#define 	DELAY_LINE_CTRL_TX_OFS	0
#define 	DELAY_LINE_CTRL_TX_MSK	0xff
#define 	DELAY_LINE_CTRL_RX_DAT_OFS	8
#define 	DELAY_LINE_CTRL_RX_DAT_MSK	0xff
#define 	DELAY_LINE_CTRL_RX_CMD_OFS	16
#define 	DELAY_LINE_CTRL_RX_CMD_MSK	0xff
#define		DELAY_LINE_TUNING_SEL		BIT(24)

/**
* PHY_CTRL2
*/

#define	EMMC_CLK_STOP_EN		BIT(3)
#define	TX_SAMPLE_CLK_X2		BIT(2)
#define	TX_SAMPLE_CLK_X1		BIT(1)
#define	TX_SAMPLE_CLK_SEL		BIT(0)


struct sdhci_fh_host {
	struct platform_device *pdev;
	void __iomem *phy_base;	/* emmc phy mapped address enable*/
	struct clk *apb_slave_en;	/* emmc tm clock enable*/
	struct clk *emmc_clk;	/* emmc main clock */
	struct clk *emmc_apb_clk;	/* emmc apb clock */
	struct clk *emmc_2x_clk;	/* emmc 2x clock */
	int phy_irq;		/* phy irq */
	int wakeup_irq;		/* wakeup irq */
	struct mmc_host *mmc;
};

void mc_init_phy(struct sdhci_fh_host *fh_emmc_host)
{
	u32 ctrl;
	
	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl &= ~(EMMC_PHY_MODE |  RX_CLK_SEL_DAT | RX_CLK_SEL_CMD);
	ctrl |= TX_CLK_GATE | RX_DL_BYPASS_DAT | RX_DL_BYPASS_CMD | TX_DL_BYPASS;
	/*ctrl |= RX_CLK_INV_EN_DAT | RX_CLK_INV_EN_CMD;*/
	/*ctrl &= ~((RX_CLK_INV_EN_DAT | RX_CLK_INV_EN_CMD));*/
	ctrl |= (RX_CLK_INV_EN_DAT | RX_CLK_INV_EN_CMD);
	ctrl |= (TX_CLK_INV_EN);
	/*ctrl &= ~((TX_CLK_INV_EN));*/

	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	ctrl |= FIFO_CLR;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl &= ~FIFO_CLR;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_IE_CTRL);
	ctrl &= ~CARD_RESET_N;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_IE_CTRL);

	ctrl |= CARD_RESET_N;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_IE_CTRL);

	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl |= SAMPLE_EN | DAT_INPUT_EN;
	ctrl &= ~(OUTPUT_BIT_MODE_MSK);
	ctrl |= 3 << OUTPUT_BIT_MODE_OFS;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_INT_CTRL2);
	ctrl |= (TX_SAMPLE_CLK_X1 | EMMC_CLK_STOP_EN);
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_INT_CTRL2);

}

void fh_sdhci_set_ddr_clock(struct sdhci_fh_host *fh_emmc_host, u32 clock)
{
	u32 ctrl;

	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl &= (~(RX_CLK_INV_EN_DAT | RX_CLK_INV_EN_CMD));
	ctrl &= (~(TX_CLK_INV_EN));
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	ctrl &= ~(SAMPLE_EN);
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl &= ~(DAT_INPUT_EN);
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	fh_pmu_set_emmc_clock(clock);

	/*config tx dat dll*/
	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_DL_CTRL);
	/*100m set*/
	ctrl |= 0x74;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_DL_CTRL);

	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl &= (~TX_DL_BYPASS);
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	/*config rx dat dll*/
	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_DL_CTRL);
	ctrl |= (0x40 << 8);
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_DL_CTRL);

	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl &= (~RX_DL_BYPASS_DAT);
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	ctrl |= FIFO_CLR;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl &= ~FIFO_CLR;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl |= DAT_INPUT_EN;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl |= SAMPLE_EN;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	writel_relaxed(0x0, fh_emmc_host->phy_base + EMMC_PHY_INT_CTRL2);
	ctrl = TX_SAMPLE_CLK_SEL | EMMC_CLK_STOP_EN;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_INT_CTRL2);
	ctrl |= TX_SAMPLE_CLK_X2;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_INT_CTRL2);
#if 1
	ctrl = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	ctrl |= RX_CLK_SEL_DAT;
	writel_relaxed(ctrl, fh_emmc_host->phy_base + EMMC_PHY_CTRL);
#endif

}
void fh_sdhci_set_clock(struct sdhci_fh_host *fh_emmc_host, u32 clock)
{
	unsigned int cfg;

	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	cfg &= ~SAMPLE_EN;
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	cfg &= ~DAT_INPUT_EN;
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	fh_pmu_set_emmc_clock(clock);

	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	cfg &= ~((TX_CLK_INV_EN));
	cfg &= ~((RX_CLK_INV_EN_DAT | RX_CLK_INV_EN_CMD));
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	/*config tx dat dll*/
	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_DL_CTRL);
	cfg |= 0x18;
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_DL_CTRL);

	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	cfg &= (~TX_DL_BYPASS);
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	/*config rx dat dll*/
	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_DL_CTRL);
	cfg |= (0x44 << 8);
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_DL_CTRL);

	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	cfg &= (~RX_DL_BYPASS_DAT);
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	cfg |= FIFO_CLR;
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	cfg &= ~FIFO_CLR;
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	cfg |= DAT_INPUT_EN;
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_CTRL);

	cfg = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_CTRL);
	cfg |= SAMPLE_EN;
	writel_relaxed(cfg, fh_emmc_host->phy_base + EMMC_PHY_CTRL);
}

void sdhci_mc_set_clock(struct sdhci_host *host, unsigned int clock)
{
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_fh_host *fh_emmc_host = sdhci_pltfm_priv(pltfm_host);
	u16 clk;
	int ret;

	unsigned long timeout;

	if (clock == 0)
		return;

	sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);

	host->mmc->actual_clock = 0;
	clk = sdhci_calc_clk(host, clock, &host->mmc->actual_clock);

	if (host->timing == MMC_TIMING_MMC_HS400)
		fh_sdhci_set_ddr_clock(fh_emmc_host, clock);
	else
		fh_sdhci_set_clock(fh_emmc_host, clock);

	clk |= SDHCI_CLOCK_INT_EN;
	sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);

	/* Wait max 20 ms */
	timeout = 20;
	while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
		& SDHCI_CLOCK_INT_STABLE)) {
		if (timeout == 0) {
			pr_err("%s: Internal clock never stabilised.\n",
			       mmc_hostname(host->mmc));
			return;
		}
		timeout--;
		spin_unlock_irq(&host->lock);
		usleep_range(900, 1100);
		spin_lock_irq(&host->lock);
	}

	clk |= SDHCI_CLOCK_CARD_EN;
	sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);

	return;
}

static irqreturn_t sdhci_mc_phy_irq(int irq, void *data)
{
	/*struct sdhci_host *host = (struct sdhci_host *)data;*/

	return IRQ_HANDLED;
}

static const struct sdhci_ops sdhci_mc_ops = {
	.reset = sdhci_reset,
	.set_clock = sdhci_mc_set_clock,
	.set_bus_width = sdhci_set_bus_width,
	.set_uhs_signaling = sdhci_set_uhs_signaling,
	.get_max_clock		= sdhci_pltfm_clk_get_max_clock,
};

static const struct sdhci_pltfm_data sdhci_mc_pdata = {
	.quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION |
		  SDHCI_QUIRK_NO_CARD_NO_RESET |
		  SDHCI_QUIRK_SINGLE_POWER_WRITE ,
	.ops = &sdhci_mc_ops,
	/*.quirks2 = SDHCI_QUIRK2_BROKEN_HS200,*/
};

static int sdhci_mc_probe(struct platform_device *pdev)
{
	struct sdhci_host *host;
	struct sdhci_pltfm_host *pltfm_host;
	struct sdhci_fh_host *fh_emmc_host;
	int ret;
	u16 host_version;
	u32 phy_version;
	struct resource *regs;
	u32 reg;

	host = sdhci_pltfm_init(pdev, &sdhci_mc_pdata, sizeof(*fh_emmc_host));
	if (IS_ERR(host))
		return PTR_ERR(host);

	pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
	pltfm_host = sdhci_priv(host);
	fh_emmc_host = sdhci_pltfm_priv(pltfm_host);
	fh_emmc_host->mmc = host->mmc;
	fh_emmc_host->pdev = pdev;
	host->mmc->caps |= (MMC_CAP_NONREMOVABLE | MMC_CAP_8_BIT_DATA);
	host->mmc->caps2 |= (MMC_CAP2_NO_SDIO | MMC_CAP2_NO_SD);
	//host->mmc->caps2 |= (MMC_CAP2_NO_SDIO | MMC_CAP2_NO_SD | MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_ES);
	host->mmc->ocr_avail = MMC_VDD_165_195;
	/*host->mmc->caps2 |= MMC_CAP2_NO_PRESCAN_POWERUP;*/
	/*host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE;*/
	fh_emmc_host->mmc->f_min = 100000;
	fh_emmc_host->mmc->f_max = 200000000;

	fh_emmc_host->emmc_clk= clk_get(NULL, "emmc_clk");
	if (IS_ERR(fh_emmc_host->emmc_clk)) {
		pr_err("emmc_clk not found continue\n");
	} else
		clk_prepare_enable(fh_emmc_host->emmc_clk);

	fh_emmc_host->emmc_apb_clk = clk_get(NULL, "emmc_apb_gate");
	if (IS_ERR(fh_emmc_host->emmc_apb_clk)) {
		pr_err("emmc_apb_clk not found continue\n");
	} else
		clk_prepare_enable(fh_emmc_host->emmc_apb_clk);
	fh_emmc_host->emmc_2x_clk = clk_get(NULL, "emmc_2x_clk");
	if (IS_ERR(fh_emmc_host->emmc_2x_clk))
		pr_err("emmc_2x_clk not found continue\n");
	else
		clk_prepare_enable(fh_emmc_host->emmc_2x_clk);
	fh_pmu_emmc_reset();
	fh_pmu_set_emmc_sample_delay1(0x4);
	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!regs) {
		pr_err("request resource error!\n");
		ret = -ENXIO;
		goto clk_disable;
	}

	host->ioaddr = ioremap_nocache(regs->start,
		regs->end - regs->start + 1);
	if (!host->ioaddr) {
		pr_err("no mem for fh emmc base!\n");
		ret = -ENOMEM;
		goto clk_disable;
	}

	regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	if (!regs) {
		pr_err("request resource error!\n");
		ret = -ENXIO;
		goto clk_disable;
	}
	fh_emmc_host->phy_base = ioremap_nocache(regs->start,
		regs->end - regs->start + 1);
	if (!fh_emmc_host->phy_base) {
		pr_err("no mem for fh emmc phy base!\n");
		ret = -ENOMEM;
		goto clk_disable;
	}

	host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION));
	dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n",
		host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >>
			       SDHCI_VENDOR_VER_SHIFT));

	phy_version = readl_relaxed(fh_emmc_host->phy_base + EMMC_PHY_VERSION_ID);
	dev_dbg(&pdev->dev, "MCI Version: 0x%08x\n",
		phy_version);

	host->irq = platform_get_irq(pdev, 0);
	host->irq = irq_create_mapping(NULL, host->irq);
	if (host->irq < 0) {
		printk(KERN_ERR "no emmc host IRQ defined!\n");
		goto clk_disable;
	}

	fh_emmc_host->phy_irq = platform_get_irq(pdev, 1);
	fh_emmc_host->phy_irq = irq_create_mapping(NULL, fh_emmc_host->phy_irq);
	if (fh_emmc_host->phy_irq < 0) {
		printk(KERN_ERR "no emmc phy IRQ defined!\n");
		goto clk_disable;
	}
	ret = devm_request_threaded_irq(&pdev->dev, fh_emmc_host->phy_irq, NULL,
					sdhci_mc_phy_irq, IRQF_ONESHOT,
					dev_name(&pdev->dev), host);
	if (ret) {
		dev_err(&pdev->dev, "Request IRQ failed (%d)\n", ret);
		goto clk_disable;
	}
	
	fh_emmc_host->wakeup_irq = platform_get_irq(pdev, 2);
	fh_emmc_host->wakeup_irq = irq_create_mapping(NULL, fh_emmc_host->wakeup_irq);
	if (fh_emmc_host->wakeup_irq < 0) {
		printk(KERN_ERR "no emmc phy wakeup IRQ defined!\n");
		goto clk_disable;
	}
	mc_init_phy(fh_emmc_host);

	ret = sdhci_add_host(host);
	if (ret)
		goto clk_disable;

	return 0;

clk_disable:
	sdhci_pltfm_free(pdev);
	return ret;
}

static int sdhci_mc_remove(struct platform_device *pdev)
{
	struct sdhci_host *host = platform_get_drvdata(pdev);
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_fh_host *fh_emmc_host = sdhci_pltfm_priv(pltfm_host);
	int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) ==
		    0xffffffff);

	sdhci_remove_host(host, dead);
	sdhci_pltfm_free(pdev);
	return 0;
}

static const struct of_device_id sdhci_mc_dt_match[] = {
	{ .compatible = "mc,sdhci" },
	{},
};

MODULE_DEVICE_TABLE(of, sdhci_mc_dt_match);

static struct platform_driver sdhci_mc_driver = {
	.probe = sdhci_mc_probe,
	.remove = sdhci_mc_remove,
	.driver = {
		   .name = "fh_sdhci",
		   .of_match_table = sdhci_mc_dt_match,
	},
};

#ifdef CONFIG_DEFERRED_INITCALLS_EMMC
deferred_module_platform_driver(sdhci_mc_driver);
#else
module_platform_driver(sdhci_mc_driver);
#endif

MODULE_DESCRIPTION("Secure Digital Host Controller Interface driver");
MODULE_LICENSE("GPL v2");

